//
//  GameViewController.m
//  TengineTwo
//
//  Created by StandardUser on 10/02/2012.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
//  the controller class, binds the models to the view
//  this code assumes that cocos uses a hash to getChildbyID
//  this class uses the Entity protocol to maintain weak coupling with models and views with the aim of enhancing portability.
//

#import "GameViewController.h"
#import "TengineModel.h"
#import "PoolingModel.h"
#import "GhostComponentFactory.h"
#import "BulletComponentFactory.h"
#import "PlayerTargetingModel.h"
#import "CCLayer.h"
#import "EntityViewComponent.h"
#import "EntityModelFactory.h"
#import "BulletModelFactoryWrapper.h"
#import "CGPointExtension.h"
#import "PlayerModule.h"
#import "CameraModel.h"
#import "CCDirector.h"

@interface GameViewController() 

@property(readwrite, nonatomic, retain) TengineModel *timeEngine;
@property(readwrite, nonatomic, retain) PoolingModel *ghostPool;
@property(readwrite, nonatomic, retain) PoolingModel *bulletPool;
@property(readwrite, nonatomic, retain) PoolingModel *bulletModelPool;
@property(readwrite, nonatomic, retain) PlayerTargetingModel *targetingModel;

@property(readwrite, nonatomic, retain) CameraModel *cam;
@property(readwrite, nonatomic, retain) CCLayer *view;
@property(readwrite, nonatomic, retain) TouchLayer *inputLayer;

@property(readwrite, nonatomic, retain) EntityModel *playerModel;

//keep a hashtable of cocos2d children - amazingly this isn't how cocos2d implements this...
@property(readwrite, nonatomic, retain) NSMutableDictionary *viewComponentHash;
-(EntityViewComponent*)getViewComponentForCachedTag:(NSString*)tag;

//debug
@property(readwrite, nonatomic, retain) NSMutableArray *tempGhostArray;
@property(readwrite, nonatomic) BOOL isSlowing;
@property(readwrite, nonatomic) CGPoint touchOffset;

@end

@implementation GameViewController

@synthesize timeEngine;
@synthesize ghostPool;
@synthesize bulletPool;
@synthesize bulletModelPool;
@synthesize targetingModel;

@synthesize cam;
@synthesize view; //view mediated by this view controller
@synthesize inputLayer;

@synthesize playerModel;

@synthesize viewComponentHash;

@synthesize audioController;

//debug
@synthesize tempGhostArray;
@synthesize isSlowing;
@synthesize touchOffset;

- (id)init {
    self = [super init];
    if (self) {
        timeEngine = [[TengineModel alloc] init];
        ghostPool = [[PoolingModel alloc] initWithFactory:[GhostComponentFactory class] PoolSize:10 withMaxSize:10];
        bulletPool = [[PoolingModel alloc] initWithFactory:[BulletComponentFactory class] PoolSize:1000 withMaxSize:1000];
        bulletModelPool = [[PoolingModel alloc] initWithFactory:[BulletModelFactoryWrapper class] PoolSize:1000 withMaxSize:1000];
        
        targetingModel = [[PlayerTargetingModel alloc] init];
        
        CGSize windowSize = [CCDirector sharedDirector].winSizeInPixels;
        cam = [[CameraModel alloc] initWithWidth:windowSize.width andHeight:windowSize.height];
        view = [[CCLayer alloc] init];
        [self addChild:view];
        
        inputLayer = [[TouchLayer alloc] init];
        inputLayer.delegate = self;
        [self addChild:inputLayer];
        
        viewComponentHash = [[NSMutableDictionary alloc] init];
        
        [self schedule:@selector(update:) interval:0];//using zero for the interval will just sync us with the default refresh, usually every 1/60 second
        
        //demo
        playerModel = [self addEntityOfType:PLAYER];
        //playerModel.origin = ccp(32, 160);
        playerModel.position = ccp(32, windowSize.height * 0.5);
        
        EntityModel *ghostOne = [self addEntityOfType:GHOST];
        ghostOne.origin = ccp(0, windowSize.height * 0.5); 
        
        EntityModel *ghostTwo = [self addEntityOfType:GHOST];
        ghostTwo.origin = ccp(20, windowSize.height * 0.5); 
        
        tempGhostArray = [[NSMutableArray alloc] initWithObjects:ghostOne, ghostTwo, nil];
        isSlowing = NO;
        
        timeEngine.clockMultiplier = 1.0;
        touchOffset = ccp(0,0);
    }
    return self;
}

- (void)dealloc {
   
    [ghostPool release];
    [bulletPool release];
    [timeEngine release];
    [viewComponentHash release];
    [targetingModel release];
    [cam release];
    
    //lazy initialiser
    if(audioController) [audioController release];
    
    [super dealloc];
}

#pragma mark handlers

//override
-(void) update:(ccTime)dt
{
    if(isSlowing)   timeEngine.clockMultiplier -= 0.01;
    else            timeEngine.clockMultiplier += 0.01;
    if(timeEngine.clockMultiplier < 0.1) timeEngine.clockMultiplier = 0.1;
    if(timeEngine.clockMultiplier > 1)  timeEngine.clockMultiplier = 1.0;
    
    if(audioController) audioController.playbackRate = timeEngine.clockMultiplier;
    
    [timeEngine update];
    
    [targetingModel clearTargets];
    [targetingModel acquireTargetsFrom:tempGhostArray];
    
    cam.zoom = 2.0 - timeEngine.clockMultiplier;
    cam.position = playerModel.position;
    [cam applyCameraTransformTo:view];
}

#pragma mark viewControllerDelegate

-(EntityModel*)addEntityOfType:(EntityType)type
{
    EntityModel *model = nil;
    EntityViewComponent *entityComponent = nil;
    switch (type) 
    {
        case BULLET: //use bullet model factory
            model = (EntityModel*)[bulletModelPool checkOut];
            entityComponent = (EntityViewComponent*)[bulletPool checkOut];
            break;
        case GHOST: //use *standard* model factory
            model = [EntityModelFactory createFromType:type];
            entityComponent = (EntityViewComponent*)[ghostPool checkOut];
            break;
        case PLAYER:
            model = [EntityModelFactory createFromType:type];
            ((PlayerModule*)model.updater).targetModel = targetingModel;
            entityComponent = [[EntityViewComponent alloc] initWithFile:@"playerSprite.png"];
            [entityComponent autorelease];
        default:
        case UNDEFINED:
            NSLog(@"unrecognised or uninitialised entity type");
            break;
    }
    
    if(entityComponent)
    {
        model.controller = self;
        [timeEngine addEntity:model]; //time engine wildefine the tag
        entityComponent.cachedTag = model.cachedTag;
        
        //NSLog(@"add entitycomponent tag:%@ model:%@", entityComponent.cachedTag, model.cachedTag);
    
        //add to cocos display list
        [view addChild:entityComponent];
        //but use hash to map tag to component for faster access
        [viewComponentHash setValue:entityComponent forKey:entityComponent.cachedTag];
    }
    
    //this is autoreleased
    return model; //we don't want entity models to get pointers to views
}

-(void)removeEntity:(id<Entity>)e
{
    NSLog(@"remove entitycomponent tag:%@", e.cachedTag);
    
    EntityViewComponent *entityComponent = [self getViewComponentForCachedTag:e.cachedTag];
    if(nil == entityComponent) return;
    
    [entityComponent retain]; //make sure we are going to hang on to it after we remove it from cocos view hierarchy
    [view removeChild:entityComponent cleanup:YES]; //no is faster ?
    [viewComponentHash removeObjectForKey:entityComponent.cachedTag];
    
    //remove view component
    switch (entityComponent.type) {
        case GHOST://just remove model from engine...
            
            [timeEngine removeEntityByTag:e.cachedTag];
            [ghostPool checkIn:entityComponent];
            
            break;
        case BULLET://check model back in to pool...
            
            [e retain];
            [timeEngine removeEntityByTag:e.cachedTag];
            [bulletModelPool checkIn:e];
            [e release];
            
            [bulletPool checkIn:entityComponent];
            break;
        default:
            NSLog(@"unrecognised or uninitialised entity type");
            break;
    }
    
    //ok we checked it back in now so we don't need it.
    [entityComponent release];
}

-(void)updateEntity:(id<Entity>)e
{
    //won't find a relevent view component if it is not visible, because one won't have been checked out.
    id<Entity> entityComponent = [self getViewComponentForCachedTag:e.cachedTag];
    
    if(nil == entityComponent) return;
    
    entityComponent.position = e.position;
    entityComponent.rotation = e.rotation;
    entityComponent.scale    = e.scale;
}

//show hide is seperate to update because it doesn't need to be called on every tick
-(void)hide:(BOOL)state Entity:(id<Entity>)e
{
    EntityViewComponent* entityComponent = [self getViewComponentForCachedTag:e.cachedTag];
    
    if(YES == state)
    {
        if(entityComponent == nil) 
        {
            switch (e.type) 
            {
                case BULLET:
                    entityComponent = (EntityViewComponent*)[bulletPool checkOut];
                    break;
                case GHOST:
                    entityComponent = (EntityViewComponent*)[ghostPool checkOut];
                    break;
                default:
                case UNDEFINED:
                    NSLog(@"unrecognised or uninitialised entity type");
                    break;
            }
            
            //*re assign identity*/
            entityComponent.cachedTag = e.cachedTag;
            [view addChild:entityComponent];
            [viewComponentHash setObject:entityComponent forKey:entityComponent.cachedTag];
        }
    }
    else if(NO == state)
    {
        if(entityComponent) 
        {
            [entityComponent retain]; //make sure we are going to hang on to it after we remove it from cocos view hierarchy
            [view removeChild:entityComponent cleanup:YES];
            [viewComponentHash removeObjectForKey:entityComponent.cachedTag];
            
            switch (entityComponent.type) {
                case GHOST:
                    [ghostPool checkIn:entityComponent];
                    break;
                case BULLET:
                    [bulletPool checkIn:entityComponent];
                    break;
                default:
                    NSLog(@"unrecognised or uninitialised entity type");
                    break;
            }
            [entityComponent release];
        }
    }
}

#pragma mark TouchLayerDelegate

-(void)onTilt:(float)amount
{
    //timeEngine.clockMultiplier = 2.0f * amount;
    //NSLog(@"tilt:%f", amount);
}

-(void)onTouchOn:(CGPoint)location
{
    touchOffset = ccpSub(location, playerModel.position);
    isSlowing = YES;
}

-(void)onTouchMove:(CGPoint)location
{
    playerModel.position = ccpSub(location, touchOffset);
}

-(void)onTouchOff
{
    isSlowing = NO;
}

-(void)onMultiTouchOn
{
    //isSlowing = YES;
}

-(void)onMultiTouchOff
{
    //isSlowing = NO;
}

#pragma mark -

-(EntityViewComponent*)getViewComponentForCachedTag:(NSString*)tag
{
    return [viewComponentHash objectForKey:tag];
}

@end
